premature optimization is the root of all evil
by Donald Knuth
最近在尝试重构EE-Book,每过一段时间看以前写的代码是一件很有意思的事情,这是跟愚蠢的自己沟通的过程,显然这也是进步的过程。
扯远了,这一篇是要总结+介绍一款分析源代码的工具。在分析EE-Book代码的过程中,我发现了两款工具,一款是Pyreverse,另一款是PyCallGraph。Pyreverse可以生成UML类图,它已集成到pylint中,它的作用是分析项目代码生成像这样的UML图:
来自pylint
显然,这个工具能够帮助我们很快地理解一个Python项目的结构,有了这样的UML类图再去看源代码就会清晰很多。但目前我想要分析的是自己的代码,虽然Pyreverse很屌,但用不上啊,所以,这个工具暂且放一放,以后需要再学习一个,多说一句,如果想画UML也可以用plantuml。
今天的主角是强大的PyCallGraph,一句话简介:PyCallGraph可以动态生成python程序的调用图。它能告诉你各函数调用次数、调用时间。先拿官方的说明举例吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #!/usr/bin/env python2 # -*- coding: utf-8 -*-
import time
from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutput
class Banana:
def __init__(self): pass
def eat(self): self.secret_function() self.chew() self.swallow()
def secret_function(self): time.sleep(0.2)
def chew(self): pass
def swallow(self): pass
graphviz = GraphvizOutput(output_file='filter_none.png')
with PyCallGraph(output=graphviz): banana = Banana() banana.eat()
|
现在有一个Banana类,我想要生成eat方法的调用关系,看看时间都用在哪里。只需要通过GraphvizOutput指定output文件,进行初始化,通过Python的with关键字,将运行的代码放入with代码块即可生成调用关系图:
可以看到,secret_funcition这部分是红色的,运行时间是最长的,如果是优化实际项目,我们就应该从这入手了。
是不是非常简单,非常优雅?就这么一张图,告诉了我们程序的运行路径,经过的每个函数、每个模块、运行的时间、调用的次数,简简单单就了解了程序的整个调用过程,其实坑还是有的,比如我生成EE-Book爬取简书某博主的文章的调用关系图:
图片太大,请点击查看
完全没法分析是不是,对的,我们和PyCallGraph的作者想到一起去了,稍微大点的项目,有的函数是不需要分析的,不然生成的图太复杂了,得有个过滤机制才行,暂时不分析一些函数,对调用的深度也得加个限制,写一个过滤函数的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| config = Config(max_depth=9) config.trace_filter = GlobbingFilter(exclude=[ 'bs4.*', 'BeautifulSoup.*', # '.epub.*', # '__main__', 'pycallgraph.*', '*.closeEvent', '*.<module>', 'src.tools.match.*', '*.EEBook.__init__', 'src.main.init_database', 'src.tools.config_load', 'src.utils.log.*', 'src.tools.path.*', 'src.tools.db.*', 'src.book.*', 'src.container.*', 'src.url_parser.*', 'src.tools.config.*', 'src.lib.epub.*', 'src.tools.html_creator.*', '*.JianshuAuthorWorker.__init__', '*.start_create_work_list', '*.JianshuAuthorWorker.save', '*.JianshuAuthorWorker.add_property', '*.JianshuAuthorWorker.clear_index', '*.JianshuAuthorWorker.create_save_config', '*.JianshuAuthorWorker.clear_work_set', '*.print_in_single_line', '*.http.set_cookie', '*.html5lib.constants.*', ])
|
再看看生成的图:
这下就要清晰多了吧,接下来就可以继续修改过滤机制,庖丁解牛般查找问题所在。
利用PyCallGraph我们能够很快找到程序需要优化的地方,接下来选择合适的方案即可。比如如果时间大多花在I/O上,应该试试Python的多线程模块,如果解析字符串花的时间比较多,可能我们得放弃bs4库,改成正则表达式?再或者,计算密集型,尝试用C语言扩展重写?其实说到底还是要有一针见血找到问题所在的能力(体现思考深度),找到问题所在再用对应方案尝试解决(体现思考广度)。要优化代码,有PyCallGraph这样的可视化工具帮助我们剖析代码,难道还不够么?
还不够么
不够么
够么
么
欢迎用更好的工具打我脸啊~